#include "Camera.h"

CCamera::CCamera()
{
	m_angX = m_angY = m_trans = 0;
	CVertex3D position = SKE_INIT_CAMEAR_POS;
	CVertex3D viewpoint = SKE_INIT_CAMEAR_VP;
	CVector3D lookup = SKE_INIT_CAMERA_LOOKUP;
	InitCamera(position, viewpoint, lookup);
}

CCamera::~CCamera()
{

}

void CCamera::InitCamera(const CVertex3D position, const CVertex3D viewpoint, const CVector3D lookup)
{
	m_position = position;
	m_viewpoint = viewpoint;
	m_lookup = lookup;
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt(position.m_data[0], position.m_data[1], position.m_data[2],
		viewpoint.m_data[0], viewpoint.m_data[1], viewpoint.m_data[2],
		lookup.m_data[0], lookup.m_data[1], lookup.m_data[2]);
	m_angX = m_angY = m_trans = 0;
}

void CCamera::SetCameraParas()
{
	glGetIntegerv(GL_VIEWPORT, m_viewport);
	glGetDoublev(GL_MODELVIEW_MATRIX, m_mvmatrix);
	glGetDoublev(GL_PROJECTION_MATRIX, m_projmatrix);
}

//	SetCameraParas must be called before GetCameraPosition, GetGlobalPosition and GetModelViewPosition
//	mvmatrix: mvmatrix * gl -> mv
CVertex3D CCamera::GetCameraPosition()
{
// 	GLdouble invmvmatrix[16];
// 	InverseMatrix(m_mvmatrix, invmvmatrix);
// 	CVertex3D p = {
// 		invmvmatrix[12] / invmvmatrix[15],
// 		invmvmatrix[13] / invmvmatrix[15], 
// 		invmvmatrix[14] / invmvmatrix[15]};
// 	return p;
	CVertex3D p = {0, 0, 0};
	return GetGlobalPosition(p);
}

CVertex3D CCamera::GetGlobalPosition(CVertex3D mvPosition)
{
	GLdouble invmvmatrix[16];
	InverseMatrix(m_mvmatrix, invmvmatrix);

	CVertex3D column[4];
	for (int i = 0; i < 4; i++)
	{
		column[i].SetValue(invmvmatrix[i * 4], invmvmatrix[i * 4 + 1], invmvmatrix[i * 4 + 2]);
	}
	CVertex3D p = {0, 0, 0};
	double w = 0;
	for (int i = 0; i < 3; i++)
	{
		p = p + column[i] * mvPosition.m_data[i];
		w += invmvmatrix[i * 4 + 3] * mvPosition.m_data[i];
	}
	p = p + column[3];
	w += invmvmatrix[15];
	p = p / w;
	return p;
}

CVertex3D CCamera::GetModelViewPosition(CVertex3D glPosition)
{
	CVertex3D column[4];
	for (int i = 0; i < 4; i++)
	{
		column[i].SetValue(m_mvmatrix[i * 4], m_mvmatrix[i * 4 + 1], m_mvmatrix[i * 4 + 2]);
	}
	CVertex3D p = {0, 0, 0};
	double w = 0;
	for (int i = 0; i < 3; i++)
	{
		p = p + column[i] * glPosition.m_data[i];
		w += m_mvmatrix[i * 4 + 3] * glPosition.m_data[i];
	}
	p = p + column[3];
	w += m_mvmatrix[15];
	p = p / w;
	return p;
}

void CCamera::SetViewPort(double cx, double cy)
{
	glViewport(0, 0, cx, cy);
}

void CCamera::SetPerspectiveParas(double hAngle, double ratio, double zNear, double zFar)
{
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(hAngle * 2, ratio, zNear, zFar);
}

//	SetCameraParas must be called before Project and UnProject

CVertex2D CCamera::Project(CVertex3D p)
{
	GLdouble winx, winy, winz;
	gluProject(p.m_data[0], p.m_data[1], p.m_data[2], m_mvmatrix, m_projmatrix, m_viewport, 
		&winx, &winy, &winz);
	CVertex2D p2D = {(double)winx, (double)winy};
	return p2D;
}

CVertex3D CCamera::UnProject(CVertex2D p)
{	
	GLdouble winx, winy, winz;	
	gluUnProject(p.m_data[0], p.m_data[1], 0, m_mvmatrix, m_projmatrix, m_viewport, 
	&winx, &winy, &winz);
	CVertex3D p3D = {(double)winx, (double)winy, (double)winz};
	return p3D;
}

void CCamera::ChangeViewPoint(double dAngX, double dAngY, double dTrans)
{
	m_angX += dAngX;
	m_angY += dAngY;
	m_trans += dTrans;
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt(m_position.m_data[0], m_position.m_data[1], m_position.m_data[2],
		m_viewpoint.m_data[0], m_viewpoint.m_data[1], m_viewpoint.m_data[2],
		m_lookup.m_data[0], m_lookup.m_data[1], m_lookup.m_data[2]);
	glRotated(m_angX * SKE_CAMERA_X_ROT_RATE, 1, 0, 0);
	glRotated(m_angY * SKE_CAMERA_Y_ROT_RATE, 0, 1, 0);
	SetCameraParas();
	CVertex3D origin = {0, 0, 0};
	CVector3D v = GetCameraPosition() - origin;
	double length = v.CalcLength();
	double ratio = m_trans * SKE_CAMERA_TRANS_RATE / length;
	glTranslated(v.m_data[0] * ratio, v.m_data[1] * ratio, v.m_data[2] * ratio);
}
